JavaScript 核心 - 來講講提升(hoisting)


Posted by ai86109 on 2020-09-29

前言

先看看這段程式碼

console.log(a)
var a = 10

照 JavaScript 逐行執行的規則,應該會印出 a is not defined,但這邊卻印出 undefined。

這裡的變數宣告被提升到最上面,這個現象就是 hoisting

如果你只想了解最基本的提升,就差不多是這樣了。


你真的懂 hoisting 嗎?

其實深入去研究 hoisting,才知道有很多概念沒有搞懂。

首先,hoisting 是跟變數有關的,所以他只會發生在他的 scope 裡面

var a = 'global'

function test(){
  console.log(a)
  var a = 'local'
}

test()

可能會很多人以為印出的是 global

但剛說到 hoisting 只會發生在他的 scope 內,所以你可以把上面那段看成以下:

var a = 'global'

function test(){
  var a
  console.log(a)
  a = 'local'
}

test()

印出的就會是 undefined

-

那再來看一個

function test(){
  console.log(a)
  var a = 'local'
  function a(){

  }
}

test()

這邊會印出 function: a

你可能會覺得是順序的問題,但把兩個宣告互換,一樣會得到 function: a,代表 function 會佔有優先權。

那如果有兩個 function 呢?

後面宣告的會蓋掉前面的,很合理。

這邊來說明一下優先順序:

  1. function
  2. argument (傳入 function 的參數)
  3. var

但要注意的是,如果 a 重新宣告

function test(){
  var a = '456'
  console.log(a)
}

test(123)

答案就會變成 456,因為 a 又重新宣告,並且有值。

所以說沒有值就不會影響嗎? 對!

function test(){
  var a
  console.log(a)
}

test(123)

會印出 123,而忽略掉沒賦值的 a


Hoisting 運作的規則

那 hoisting 實際上是怎麼運作的呢?

根據 ECMAScript 的規範,每當你進入一個 function 的時候,就會產生一個 EC(Execution Contexts),並且把 EC 放到 stack 中,一層一層疊上去,EC 裡面包含這個 function 的所有資訊。

最上層的是正在運行的 EC,執行完畢便會抽離;最底層的則是 Global Execution Contexts。

那我們剛剛說到 EC 裡面包含這個 function 的所有資訊,指的是什麼呢?

每個 EC 都有 VO(Variable Object),裡面所有宣告的變數或 function 都會變成他的 properties。

以這個程式碼為例:

function test(){
  var a = 1
}

他的 VO 就可以看成像這樣的物件

VO: {
  a: 1
}

-
當我們 call 這個 function 並帶參數時,他便會幫參數初始化

function test(a, b){

}

test(123)

他的VO就會長成以下這樣:

VO: {
  a: 123
  b: undefined
}

有傳的參數就會寫入並且賦值,沒傳的就會初始化成 undefined

-

而當我們進入 EC 時,初始化後如果碰到同名的 function 時,這個值就會被取代成這個 function。

function test(a){
  function a(){

  }
}

test(123)

他的 VO 就會長成這樣

VO: {
  a: pointer to function a
}

進入 EC,處理完 function 的 case 後,便會開始初始化宣告的變數。

如果這個宣告的變數,在 VO 已經存在且有值,則忽略這個宣告。

如果發現這個宣告的變數 VO 裡面沒有,則初始化這個宣告成 undefined,接下來才開始逐行執行。


let & const 的 hoisting

有發現講了這麼久的 hoisting,卻一直沒提到 let 和 const 嗎?

那是因為他們的機制有些許不同

先看一段程式碼

function test(){
  console.log(a)
  let a = 10
}

test()

按照之前的邏輯你應該會認為要印出 undefined

但實際上會印出 a is not defined

這是為什麼呢?

難道 let & const 沒有 hoisting 嗎?

其實是有的!一樣可以看成以下

function test(){
  let a
  console.log(a)
  a = 10
}

test()

但是不一樣的是,從進入這個 EC 到 a 被賦值之前,你不能夠去存取 a,否則就會出現錯誤。

而進入這個 EC 到 a 被賦值之前的這個區間,被稱為 TDZ(Temporal Dead Zone)

以上的內容是參考 Huli 大大的文章所寫下的筆記,更詳細、深入的可以參考這篇:我知道你懂 hoisting,可是你了解到多深?


#hoisting #javascript







Related Posts

AJAX JSON 傳遞

AJAX JSON 傳遞

React Native AppState 狀態介紹

React Native AppState 狀態介紹

01. Install Python, Flask and virtual environment

01. Install Python, Flask and virtual environment


Comments